package com.fzrskj.cloud.free.user.utils.proin;

import com.fzrskj.cloud.free.core.tool.CDateTimeTool;
import ms.core.tool.StrTool;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;

/**
 * ExcelReadUtils.
 *
 * @author zj.
 *         Created on 2019/4/18 0018.
 */
public class ExcelReadUtils {
    /**
     * 从Http请求数据中读取excel.
     * @param request http 请求信息.
     * @return 读取结果.
     */
    public static List<List<String>> readXls(HttpServletRequest request){
        List<List<String>> result = new ArrayList<>();
        try {
            // HSSFWorkbook 标识整个excel
            HSSFWorkbook hssfWorkbook = new HSSFWorkbook(request.getInputStream());
            int size = hssfWorkbook.getNumberOfSheets();
            // 循环每一页，并处理当前循环页
            for (int numSheet = 0; numSheet < size; numSheet++) {
                // HSSFSheet 标识某一页
                HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(numSheet);
                if (hssfSheet == null) {
                    continue;
                }
                // 处理当前页，循环读取每一行
                for (int rowNum = 1; rowNum <= hssfSheet.getLastRowNum(); rowNum++) {
                    // HSSFRow表示行
                    HSSFRow hssfRow = hssfSheet.getRow(rowNum);
                    int minColIx = hssfRow.getFirstCellNum();
                    int maxColIx = hssfRow.getLastCellNum();
                    List<String> rowList = new ArrayList<String>();
                    // 遍历改行，获取处理每个cell元素
                    for (int colIx = minColIx; colIx < maxColIx; colIx++) {
                        // HSSFCell 表示单元格
                        HSSFCell cell = hssfRow.getCell(colIx);
                        if (cell == null) {
                            continue;
                        }
                        rowList.add(getStringVal(cell, false));
                    }
                    result.add(rowList);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e.getCause());
        }
        return result;
    }

    /**
     * 改造poi默认的toString（）方法如下.
     *  1.对于不熟悉的类型，或者为空则返回""控制串.
     *  2.如果是数字，则修改单元格类型为String，然后返回String，这样就保证数字不被格式化了.
     * @param cell 列.
     * @return result.
     */
    private static String getStringVal(Cell cell, Boolean forceString) {
        switch (cell.getCellType()) {
            case Cell.CELL_TYPE_BOOLEAN:
                return cell.getBooleanCellValue() ? "TRUE" : "FALSE";
            case Cell.CELL_TYPE_FORMULA:
                return cell.getCellFormula();
            case Cell.CELL_TYPE_NUMERIC:
                if (cell.getDateCellValue().getTime() > 0 & !forceString) {
                    return String.valueOf(cell.getDateCellValue().getTime());
                }
                cell.setCellType(Cell.CELL_TYPE_STRING);
                return cell.getStringCellValue();
            case Cell.CELL_TYPE_STRING:
                return cell.getStringCellValue();
            default:
                return "";
        }
    }

    public static <T> List<T> readXls(HttpServletRequest request, Class<T> targetClass, String[] keys, Integer pageNum,
                                      Integer fromLineNum) {
        return readXls(request, targetClass, keys, pageNum, fromLineNum, new ArrayList<>());
    }

    public static <T> List<T> readXls(HttpServletRequest request, Class<T> targetClass, String[] keys, Integer pageNum,
                                      Integer fromLineNum, List<String> forceStringKeys) {
        List<T> result = new ArrayList<>();
        try {
            if (!ServletFileUpload.isMultipartContent(request)) {
                throw new RuntimeException("获取上传文件换败");
            }

            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            upload.setHeaderEncoding("UTF-8");

            List<FileItem> fileItemList = upload.parseRequest(request);
            for (FileItem fileItem : fileItemList) {
                System.out.print("read line: ");
                System.out.println(fileItem);
                if (fileItem.isFormField()){
                    continue;
                }
                String filename = fileItem.getName();

                // HSSFWorkbook 标识整个excel
                Workbook workbook = null;
                if (filename.endsWith("xls")) {
                    workbook = new HSSFWorkbook(fileItem.getInputStream());
                } else {
                    workbook = new XSSFWorkbook(fileItem.getInputStream());
                }
                int size = workbook.getNumberOfSheets();
                // 循环每一页，并处理当前循环页
                for (int numSheet = 0; numSheet < size; numSheet++) {
                    if ((numSheet + 1) != pageNum) {
                        continue;
                    }
                    // HSSFSheet 标识某一页
                    Sheet sheet = workbook.getSheetAt(numSheet);
                    if (sheet == null) {
                        continue;
                    }

                    // 处理当前页，循环读取每一行
                    for (int rowNum = fromLineNum; rowNum <= sheet.getLastRowNum(); rowNum++) {
                        // HSSFRow表示行
                        Row row = sheet.getRow(rowNum);

                        Map<String, String> rowMap = new HashMap<>();
                        Integer columnNumber = 0;
                        for (String key : keys) {
                            Cell cell = row.getCell(columnNumber++);
                            String value = "";
                            if (cell == null) {
                                continue;
                            }
                            value = getStringVal(cell, forceStringKeys.contains(key));
                            rowMap.put(key, value);
                        }

                        T bean = mapToClass(rowMap, targetClass);
                        result.add(bean);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e.getCause());
        }
        return result;
    }

    public static <T> T mapToClass(Map<String, ?> map, Class<T> targetClass) {
        try {
            T toObj = targetClass.newInstance();
            Field[] toFields = toObj.getClass().getDeclaredFields();

            for (Field toField : toFields) {

                if (map.containsKey(toField.getName())) {
                    toField.setAccessible(true);
                    String type = toField.getType().getName();
                    Object realValue = map.get(toField.getName());
                    switch (type) {
                        case "char":
                        case "java.lang.Character":
                        case "java.lang.String":
                            realValue = realValue;
                            break;
                        case "java.util.Date":
                            if(!isNumeric(realValue.toString())) {
                                realValue = StrTool.isBlankStr(realValue.toString()) ? null :
                                        CDateTimeTool.strToDateTime(realValue.toString());
                            } else {
                                realValue = StrTool.isBlankStr(realValue.toString()) ? null :
                                        new Date(Long.valueOf(realValue.toString()));
                            }
                            break;
                        case "java.lang.Integer":
                            realValue =  StrTool.isBlankStr(realValue.toString()) ? null : Integer.valueOf(realValue.toString());
                            break;
                        case "int":
                            realValue = StrTool.isBlankStr(realValue.toString()) ? null : Integer.valueOf(realValue.toString()).intValue();
                            break;
                        case "float":
                            realValue = StrTool.isBlankStr(realValue.toString()) ? null : Float.valueOf(realValue.toString()).floatValue();
                            break;
                        case "double":
                            realValue = StrTool.isBlankStr(realValue.toString()) ? null : Double.valueOf(realValue.toString()).doubleValue();
                            break;
                        case "java.lang.Double":
                            realValue = StrTool.isBlankStr(realValue.toString()) ? null : Double.valueOf(realValue.toString());
                            break;
                        case "java.lang.Float":
                            realValue = StrTool.isBlankStr(realValue.toString()) ? null : Float.valueOf(realValue.toString());
                            break;
                        case "java.lang.Long":
                            realValue = StrTool.isBlankStr(realValue.toString()) ? null : Long.valueOf(realValue.toString());
                            break;
                        case "java.lang.Short":
                            realValue = StrTool.isBlankStr(realValue.toString()) ? null : Short.valueOf(realValue.toString());
                            break;
                        case "java.math.BigDecimal":
                            realValue = StrTool.isBlankStr(realValue.toString()) ? null : new BigDecimal(realValue.toString());
                            break;
                        default:
                            break;
                    }
                    toField.set(toObj, realValue);
                }
            }
            return toObj;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 判断数字.
     * @param str String.
     * @return 判断结果.
     */
    public static boolean isNumeric(String str) {
        for (int i = 0; i < str.length(); i++) {
            if (!Character.isDigit(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }
}
